package com.example.demo.views;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.SortedSet;
import java.util.TreeSet;

import com.vaadin.data.Item;
import com.vaadin.data.Property;
import com.vaadin.terminal.Resource;
import com.vaadin.ui.AbstractSelect;
import com.vaadin.ui.AbstractTextField;
import com.vaadin.ui.Button;
import com.vaadin.ui.DateField;
import com.vaadin.ui.Field;
import com.vaadin.ui.Table;

/**
 * ColumnProperty's propertyId and loadData(Map<Object,Map<String, Object>> dataSet, boolean isEmpty)'s 
 * first parameter's(dataSet) value's(Map<String, Object>) key should be same.
 * @author towhid
 *
 */
public class DataEntryTable extends Table {

	private static final long serialVersionUID = -421335807193760273L;
	public final static Integer NO_DATA_FOUND_MARKER = new Integer(11199912);
	private Map<Object,Object[]> itemFieldMap = null;
	private Object selectedItemId = null;
	private List<Object> selectedItemIds = null;
	private List<ColumnProperty> tableProperties = null;
	private Object[] _NATURAL_COL_ORDER = null;
	private String[] _COL_HEADERS_ENGLISH = null;
	private DataEntryTableDelegate delegate = null;

	public DataEntryTable(List<ColumnProperty> properties){
		super();
		setSizeFull();
		setStyleName("striped");
		
		setColumnCollapsingAllowed(false);
		setColumnReorderingAllowed(false);
		
		setSelectable(true);
		setMultiSelect(true);  
		
		setImmediate(true);
		setNullSelectionAllowed(false);
		
		setPageLength(0);
		addListener(new TableValueChangeListener());
		
		setTableProperties(properties);
		setTableContainerProperties();
	}
	
	public DataEntryTable(List<ColumnProperty> properties, String styleName){
		this(properties);
		if(styleName != null && styleName.length() > 0)
			setStyleName(styleName);
	}
	
	@Override
	public void attach() {
		
		super.attach();
	}
	
	@Override
	public void addListener(ValueChangeListener listener) {
		//Caution 
		if(listener instanceof TableValueChangeListener)
			super.addListener(listener);
	}
	
	@Override
	protected String formatPropertyValue(Object rowId, Object colId,
			Property property) {
		
		String result = null;
		if(getDelegate() != null){
			result = getDelegate().formatTablePropertyValue(rowId, colId, property);
		}
		return (result == null) ? super.formatPropertyValue(rowId, colId, property) : result;
	}
	
	/*>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>Interface>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>*/
	
	public static interface DataEntryTableDelegate{
		public void onColumnFieldCreate(Object propertyId, Class<?> type, Field field);
		public String formatTablePropertyValue(Object rowId, Object colId,
				Property property);
	}
	
/*>>>>>>>>>>>>>>>>>>>>>>>>>>>>Table Manipulation>>>>>>>>>>>>>>>>>>>>>>>>>*/
	
	public final Map<Object, Object[]> unmodifiableFieldMap(){
		return Collections.unmodifiableMap(getItemFieldMap());
	}
	
	public final Map<Object,Object[]> createTableDataSource(Map<Object,Map<String, Object>> dataSet
			, boolean isEmpty){
		
		Map<Object,Object[]> data = new HashMap<Object, Object[]>(dataSet.size());
		
		for(Entry<Object,Map<String, Object>> dataX : dataSet.entrySet()){
			
			if( !isEmpty && NATURAL_COL_ORDER().length <= dataX.getValue().size()){
				
				Object[] rowProperties = new Object[NATURAL_COL_ORDER().length];
				for(int index = 0; index < NATURAL_COL_ORDER().length; index++){
					
					Object columnPropertyId = NATURAL_COL_ORDER()[index];
					ColumnProperty property = getTableProperties().get(index);
					Class<?> ty = property.getType();
					try {
						rowProperties[index] = ty.newInstance();
						if (rowProperties[index] instanceof Field) {
							
							if(rowProperties[index] instanceof AbstractTextField
									|| rowProperties[index] instanceof AbstractSelect
									|| rowProperties[index] instanceof DateField){
								
								((Field) rowProperties[index]).setValue(dataX.getValue().get(columnPropertyId));
								if(getDelegate() != null){
									getDelegate().onColumnFieldCreate(columnPropertyId, ty, (Field) rowProperties[index]);
								}
							}
							else if(rowProperties[index] instanceof Button){
								String caption = (dataX.getValue().get(columnPropertyId) != null) ? dataX.getValue().get(columnPropertyId).toString() : "Show";
								((Button) rowProperties[index]).setCaption(caption);
								((Button) rowProperties[index]).setData(dataX.getKey());
								if(getDelegate() != null){
									getDelegate().onColumnFieldCreate(columnPropertyId, ty, (Field) rowProperties[index]);
								}
							}
						}else{
							rowProperties[index] = dataX.getValue().get(columnPropertyId);
						}
					} catch (InstantiationException e) {
						rowProperties[index] = "";
						e.printStackTrace();
					} catch (IllegalAccessException e) {
						rowProperties[index] = "";
						e.printStackTrace();
					}
				}
				
				data.put(dataX.getKey(), rowProperties);
			}else{
				data.put(dataX.getKey(), getDefaultItemProperties());
			}
		}
		return data;
	}
	
	public final Map<Object,Object[]> createTableDataSource(List<Map<String, Object>> dataSet
			, boolean isEmpty){
		
		Map<Object,Object[]> data = new HashMap<Object, Object[]>(dataSet.size());
		Object lastId = lastItemId();
		int idCount = 0;
		if (lastId != null && lastId instanceof Integer){
			idCount = ((Integer)lastId) + 1;
		}
		
		for(Map<String, Object> dataX : dataSet){
			
			if( !isEmpty && NATURAL_COL_ORDER().length <= dataX.size()){
				
				Object[] rowProperties = new Object[NATURAL_COL_ORDER().length];
				for(int index = 0; index < NATURAL_COL_ORDER().length; index++){
					
					Object columnPropertyId = NATURAL_COL_ORDER()[index];
					ColumnProperty property = getTableProperties().get(index);
					Class<?> ty = property.getType();
					try {
						rowProperties[index] = ty.newInstance();
						if (rowProperties[index] instanceof Field) {
							
							if(rowProperties[index] instanceof AbstractTextField 
									|| rowProperties[index] instanceof AbstractSelect
									|| rowProperties[index] instanceof DateField){
								
								((Field) rowProperties[index]).setValue(dataX.get(columnPropertyId));
								if(getDelegate() != null){
									getDelegate().onColumnFieldCreate(columnPropertyId, ty, (Field) rowProperties[index]);
								}
							}
							else if(rowProperties[index] instanceof Button){
								String caption = (dataX.get(columnPropertyId) != null) ? dataX.get(columnPropertyId).toString() : "Show";
								((Button) rowProperties[index]).setCaption(caption);
								((Button) rowProperties[index]).setData(idCount);
								if(getDelegate() != null){
									getDelegate().onColumnFieldCreate(columnPropertyId, ty, (Field) rowProperties[index]);
								}
							}
						}else{
							rowProperties[index] = dataX.get(columnPropertyId);
						}
					} catch (InstantiationException e) {
						rowProperties[index] = "";
						e.printStackTrace();
					} catch (IllegalAccessException e) {
						rowProperties[index] = "";
						e.printStackTrace();
					}
				}
				data.put(idCount++, rowProperties);
			}else{
				data.put(idCount++, getDefaultItemProperties());
			}
		}
		return data;
	}
	
	public final void loadData(Map<Object,Object[]> data){
		
		if (data != null && data.size() > 0) {
			for (Entry<Object, Object[]> element : data.entrySet()) {
				addNewItem(element.getKey(), element.getValue());
			}
		}
	}
	
	public final void loadData(Map<Object,Map<String, Object>> dataSet, boolean isEmpty){
		
		loadData(createTableDataSource(dataSet, isEmpty));
	}
	
	public final void loadData(List<Map<String, Object>> dataSet, boolean isEmpty){
		
		loadData(createTableDataSource(dataSet, isEmpty));
	}
	
	public final void loadOrderdData(Map<Object, Object[]> data){
		
		if (data != null && data.size() > 0) {
			SortedSet<Object> sortedKeys = new TreeSet<Object>(data.keySet());
			for (Object itemId : sortedKeys) {
				addNewItem(itemId, data.get(itemId));
			}
		}
	}
	
	public final void loadOrderdData(Map<Object,Map<String, Object>> dataSet, boolean isEmpty){
		
		loadOrderdData(createTableDataSource(dataSet, isEmpty));
	}
	
	public final void loadOrderdData(List<Map<String, Object>> dataSet, boolean isEmpty){
		
		loadOrderdData(createTableDataSource(dataSet, isEmpty));
	}
	
	public final void addNewItem(Object itemId){
		
		if (itemId != null && !containsId(itemId)) {
			Object[] defaultRow = getDefaultItemProperties();
			addItem(defaultRow, itemId);
			getItemFieldMap().put(itemId, defaultRow);
		}
	}
	
	public final void addNewItem(Object itemId, Object[] columnValues){
		
		if (itemId != null && columnValues != null) {
			if (columnValues.length == NATURAL_COL_ORDER().length) {
				if (!containsId(itemId)) {
					addItem(columnValues, itemId);
					getItemFieldMap().put(itemId, columnValues);
				}else{
					
					Item item = getItem(itemId);
					for(int index =0; index < NATURAL_COL_ORDER().length; index++){
						
						Property oldProp = item.getItemProperty(NATURAL_COL_ORDER()[index]);
						oldProp.setValue(columnValues[index]);
						
					}
					getItemFieldMap().put(itemId, columnValues);
				}
			}
		}
	}
	
	public final boolean deleteSelectedItem(){
		
		Object itemId = getSelectedItemId();
		if(itemId != null && containsId(itemId)){
			Object lastItemId = lastItemId();
			boolean isOk = removeItem(itemId);
			if(isOk)
				getItemFieldMap().remove(itemId);
			if(lastItemId.equals(itemId) 
					&& getContainerDataSource().size() == 0){
				setEditable(false);
			}
			return isOk;
		}else{
			return false;
		}
	}
	
	public final void deleteSelectedItems(){
		
		Object[] itemIds = getSelectedItemIds().toArray();
		if (itemIds != null && itemIds.length > 0) {
			Object lastItemId = lastItemId();
			boolean isCarry = false; 
			for (Object itemId : itemIds) {
				if (containsId(itemId)) {
					if(removeItem(itemId))
						getItemFieldMap().remove(itemId);
				}
				isCarry = (lastItemId == itemId) ? true : false;
			}
			if(isCarry && getContainerDataSource().size() == 0){
				setEditable(false);
			}
		}
	}
	
	public final boolean deleteAllItems(boolean isEmpty){
		
		if(getItemIds().size() > 0){
			boolean isOk = removeAllItems();
			if(isOk)
				getItemFieldMap().clear();
			if(!isEmpty){
				setEditable(false);
			}
			return isOk;
		}else{
			return false;
		}
	}
	
	public DataEntryTableDelegate getDelegate() {
		return delegate;
	}

	public void setDelegate(DataEntryTableDelegate delegate) {
		this.delegate = delegate;
	}

	public final boolean isDataNotFoundRow(Object itemId){
		
		Object lastId =  (Integer)lastItemId();
		if(itemId != null){
			lastId = itemId;
		}
		if(NO_DATA_FOUND_MARKER == lastId){
			return true;
		}
		return false;
	}
	
	public final List<ColumnProperty> getUnmodifiableTableProperties() {
		return Collections.unmodifiableList(tableProperties);
	}
	
	public final Object[] NATURAL_COL_ORDER_UNMODIFIABLE() {
		
		return NATURAL_COL_ORDER();
	}

	public final String[] COL_HEADERS_ENGLISH_UNMODIFIABLE() {
		
		return COL_HEADERS_ENGLISH();
	}
	
	public final Map<Object, Object[]> getNoDataFound(){
		
		Map<Object,Object[]> noDataMarker = new HashMap<Object, Object[]>();
		noDataMarker.put(NO_DATA_FOUND_MARKER, getDefaultItemProperties());
		return noDataMarker;
	}
	
	/*>>>>>>>>>>>>>>>>>>>>>>>>>>Private & Protected Methods & Attri>>>>>>>>>>>>>>>>>>>>>>*/
	
	protected Object getSelectedItemId() {
		return selectedItemId;
	}

	protected void setSelectedItemId(Object selectedItemId) {
		this.selectedItemId = selectedItemId;
	}

	protected List<Object> getSelectedItemIds() {
		if(selectedItemIds == null){
			selectedItemIds = new ArrayList<Object>();
		}
		return selectedItemIds;
	}
	
	protected Map<Object, Object[]> getItemFieldMap() {
		if(itemFieldMap == null){
			itemFieldMap = new HashMap<Object, Object[]>();
		}
		return itemFieldMap;
	}
	
	protected List<ColumnProperty> getTableProperties() {
		return tableProperties;
	}

	private void setTableProperties(List<ColumnProperty> tableProperties) {
		this.tableProperties = tableProperties;
	}

	protected final Object[] NATURAL_COL_ORDER() {
		if(_NATURAL_COL_ORDER == null){
			_NATURAL_COL_ORDER = new Object[getTableProperties().size()];
			for (int index =0 ; index < getTableProperties().size(); index++) {
				ColumnProperty property = getTableProperties().get(index);
				_NATURAL_COL_ORDER[index] = property.getPropertyId();
			}
		}
		return _NATURAL_COL_ORDER;
	}

	protected final String[] COL_HEADERS_ENGLISH() {
		if(_COL_HEADERS_ENGLISH == null){
			_COL_HEADERS_ENGLISH = new String[getTableProperties().size()];
			for (int index = 0; index < getTableProperties().size(); index++) {
				ColumnProperty property = getTableProperties().get(index);
				_COL_HEADERS_ENGLISH[index] = property.getColumnHeader();
			}
		}
		return _COL_HEADERS_ENGLISH;
	}
	
	private class TableValueChangeListener implements ValueChangeListener{
		
		private static final long serialVersionUID = 5502544724165418645L;

		@Override
		public void valueChange(com.vaadin.data.Property.ValueChangeEvent event) {
			
			if(getSelectedItemIds().size() > 0)
				getSelectedItemIds().clear();
			
			if(isMultiSelect()){
				@SuppressWarnings("unchecked")
				Set<Object> values = (Set<Object>)event.getProperty().getValue();
				
				if(values != null && values.size() >= 1){
					getSelectedItemIds().addAll(values);
					setSelectedItemId((Object)values.toArray()[0]);
				}else{
					setSelectedItemId(null);
				}
			}
			else {
				Object value = event.getProperty().getValue();
				if(value != null){
					getSelectedItemIds().add(value);
					setSelectedItemId(value);
				}else{
					setSelectedItemId(null);
				}
			}
		}
	}
	
	private Object[] getDefaultItemProperties(){
		
		Object[] row = new Object[NATURAL_COL_ORDER().length];
		for (int index = 0; index < NATURAL_COL_ORDER().length; index++) {
			
			ColumnProperty property = getTableProperties().get(index);
			Class<?> ty = property.getType();
			try {
				row[index] = ty.newInstance();
			} catch (InstantiationException e) {
				row[index] = "";
				e.printStackTrace();
			} catch (IllegalAccessException e) {
				row[index] = "";
				e.printStackTrace();
			}
		}
		return row;
	}	
	
	private void setTableContainerProperties(){
		
		if(NATURAL_COL_ORDER().length == COL_HEADERS_ENGLISH().length){
			
			for(int index=0; index < NATURAL_COL_ORDER().length; index++){
				
				Object propertyId = NATURAL_COL_ORDER()[index];
				ColumnProperty property = getTableProperties().get(index);
				
				addContainerProperty(propertyId, property.getType(), property.getDefaultData());
				setColumnExpandRatio(propertyId, property.getExpandRatio());
				setColumnWidth(propertyId, property.getColumnWidth());
				setColumnHeader(propertyId, COL_HEADERS_ENGLISH()[index]);
				setColumnAlignment(propertyId, property.getAlignment());
			}
		}
	}
	
	/**
	 * 
	 * @author u
	 *
	 */
	
	public static final class ColumnProperty implements Serializable{

		private static final long serialVersionUID = -1154093518025191049L;
		private Object propertyId = null;
		private Object defaultData = null;
		private String columnHeader = null;
		private Class<?> type = null; 
		private Resource icon = null;
		private String alignment = null;
		private float expandRatio = 0.0f;
		private int columnWidth = 50;
		
		public ColumnProperty(Object propertyId, Class<?> type, String header){
			
			super();
			this.propertyId = propertyId;
			this.type = type;
			this.columnHeader = header;
			this.defaultData = null;
			this.icon = null;
			this.alignment = Table.ALIGN_CENTER;
		}

		public Object getDefaultData() {
			return defaultData;
		}

		public void setDefaultData(Object defaultData) {
			this.defaultData = defaultData;
		}

		public String getColumnHeader() {
			return columnHeader;
		}

		public void setColumnHeader(String columnHeader) {
			this.columnHeader = columnHeader;
		}

		public Class<?> getType() {
			return type;
		}

		public void setType(Class<?> type) {
			this.type = type;
		}

		public Resource getIcon() {
			return icon;
		}

		public void setIcon(Resource icon) {
			this.icon = icon;
		}

		public String getAlignment() {
			return alignment;
		}

		public void setAlignment(String alignment) {
			this.alignment = alignment;
		}

		public Object getPropertyId() {
			return propertyId;
		}

		public float getExpandRatio() {
			return expandRatio;
		}

		public void setExpandRatio(float expandRatio) {
			this.expandRatio = expandRatio;
		}

		public int getColumnWidth() {
			return columnWidth;
		}

		public void setColumnWidth(int columnWidth) {
			this.columnWidth = columnWidth;
		}
		
	}
	
}
